#pragma once
#include "SphereGeometry.h"
POCELAIN_NAMESPACE_BEGIN
SphereGeometry::SphereGeometry( Real radius, uint32_t widthSegments, uint32_t heightSegments, Real phiStart, Real phiLength, Real thetaStart, Real thetaLength)
    :radius(radius)
    ,widthSegments(widthSegments)
    ,heightSegments(heightSegments)
    ,phiStart(phiStart)
    ,phiLength(phiLength)
    ,thetaStart(thetaStart)
    ,thetaLength(thetaLength)
{
    //        this.type = 'SphereGeometry';
    //        this.parameters = {
    //            radius: radius,
    //            widthSegments: widthSegments,
    //            heightSegments: heightSegments,
    //            phiStart: phiStart,
    //            phiLength: phiLength,
    //            thetaStart: thetaStart,
    //            thetaLength: thetaLength
    //        };

    widthSegments = std::max<uint32_t>(3,widthSegments);
    heightSegments = std::max<uint32_t>(2,heightSegments);

    Real thetaEnd = std::min( thetaStart + thetaLength, MathUtils::PI );

    uint32_t index = 0;
    std::vector<std::vector<uint32_t>> grid;
    grid.reserve(heightSegments);

    Vector3 vertex;
    Vector3 normal;

    // buffers
    std::vector<uint32_t> indices;
    std::vector<Real> vertices;
    std::vector<Real> normals;
    std::vector<Real> uvs;

    indices.reserve(heightSegments*widthSegments*3);
    vertices.reserve(heightSegments*widthSegments*3);
    normals.reserve(heightSegments*widthSegments*3);
    uvs.reserve(heightSegments*widthSegments*2);

    // generate vertices, normals and uvs
    for ( uint32_t iy = 0; iy <= heightSegments; ++iy )
    {
        std::vector<uint32_t> verticesRow;
        verticesRow.reserve(widthSegments);

        Real v = Real(iy) / heightSegments;

        // special case for the poles
        Real uOffset = 0;
        if ( iy == 0 && thetaStart == 0 )
        {
            uOffset = 0.5 / widthSegments;
        }
        else if ( iy == heightSegments && thetaEnd == MathUtils::PI )
        {
            uOffset = - 0.5 / widthSegments;
        }
        for ( uint32_t ix = 0; ix <= widthSegments; ++ix  )
        {
            Real u = Real(ix)/widthSegments;

            // vertex

            vertex.x = - radius * std::cos( phiStart + u * phiLength ) * std::sin( thetaStart + v * thetaLength );
            vertex.y = radius * std::cos( thetaStart + v * thetaLength );
            vertex.z = radius * std::sin( phiStart + u * phiLength ) * std::sin( thetaStart + v * thetaLength );

            vertices.push_back(vertex.x);
            vertices.push_back(vertex.y);
            vertices.push_back(vertex.z);

            // normal

            normal.copy( vertex ).normalize();
            normals.push_back(normal.x);
            normals.push_back(normal.y);
            normals.push_back(normal.z);

            // uv
            uvs.push_back(u + uOffset);
            uvs.push_back(1 - v );
            verticesRow.push_back(index++);
        }

        grid.push_back( verticesRow );

    }

    // indices
    for ( auto iy = 0; iy < heightSegments; iy ++ )
    {
        for ( auto ix = 0; ix < widthSegments; ix ++ )
        {
            auto a = grid[ iy ][ ix + 1 ];
            auto b = grid[ iy ][ ix ];
            auto c = grid[ iy + 1 ][ ix ];
            auto d = grid[ iy + 1 ][ ix + 1 ];

            if ( iy != 0 || thetaStart > 0 )
            {
                indices.push_back( a);
                indices.push_back( b);
                indices.push_back( d );
            }
            if ( iy != heightSegments - 1 || thetaEnd < MathUtils::PI )
            {
                indices.push_back(b);
                indices.push_back(c);
                indices.push_back(d);
            }
        }
    }

    // build geometry
    this->setIndices(new UInt32BufferAttribute(indices,3));
    this->setAttribute( "position", new Float32BufferAttribute( vertices, 3 ) );
    this->setAttribute( "normal", new Float32BufferAttribute( normals, 3 ) );
    this->setAttribute( "uv", new Float32BufferAttribute( uvs, 2 ) );

}
POCELAIN_NAMESPACE_END
